﻿// -----------------------------------------------------------------------
// <copyright file="SqlClauseBuilderTests.cs">
// Copyright (c) 2013-2014 Andrey Veselov. All rights reserved.
// License:  Microsoft Public License (MS-PL)
// Contacts: http://andrey.moveax.com  andrey@moveax.com
// </copyright>
// -----------------------------------------------------------------------

using System.Linq;

namespace Moveax.DataAccess.Cqrs.Highway.EF.UnitTests
{
	using System;
	using System.Linq.Expressions;
	using Microsoft.VisualStudio.TestTools.UnitTesting;
	using Moveax.DataAccess.Cqrs.Highway.EF.AdvancedFeatures;
	using Moveax.DataAccess.Cqrs.Highway.UnitTests.Model;

	[TestClass]
	public class SqlClauseBuilderTests
	{
		[TestMethod]
		public void SqlClauseBuilderSimpleWithConst()
		{
			// Arrange
			const string expectedSql = "(Id = '5')";
			Expression<Func<User, bool>> clause = u => u.Id == 5;

			var sqlClauseBuilder = new SqlClauseBuilder();

			// Act
			string actualSql = sqlClauseBuilder.Build(clause.Body);

			// Assert
			Assert.AreEqual(expectedSql, actualSql);
		}

		[TestMethod]
		public void SqlClauseBuilderSimpleWithVaue()
		{
			// Arrange
			string name = "some Name";
			string expectedSql = string.Format("(Name = '{0}')", name);

			Expression<Func<User, bool>> clause = u => u.Name == name;

			var sqlClauseBuilder = new SqlClauseBuilder();

			// Act
			string actualSql = sqlClauseBuilder.Build(clause.Body);

			// Assert
			Assert.AreEqual(expectedSql, actualSql);
		}

		[TestMethod]
		public void SqlClauseBuilderSimpleWithConvertFromByte()
		{
			// Arrange
			byte id = 5;
			const string expectedSql = "(Id = '5')";

			Expression<Func<User, bool>> clause = u => u.Id == id;

			var sqlClauseBuilder = new SqlClauseBuilder();

			// Act
			string actualSql = sqlClauseBuilder.Build(clause.Body);

			// Assert
			Assert.AreEqual(expectedSql, actualSql);
		}

		[TestMethod]
		public void SqlClauseBuilderSimpleWithConvertFromEnum()
		{
			// Arrange
			var userType = UserType.Reader;
			string expectedSql = string.Format("(Type = {0})", (byte)UserType.Reader);

			Expression<Func<User, bool>> clause = u => u.Type == userType;

			var sqlClauseBuilder = new SqlClauseBuilder();

			// Act
			string actualSql = sqlClauseBuilder.Build(clause.Body);

			// Assert
			Assert.AreEqual(expectedSql, actualSql);
		}

		[TestMethod]
		public void SqlClauseBuilderPropertyIsNullConst()
		{
			// Arrange
			string expectedSql = string.Format("(Name IS NULL)");

			Expression<Func<User, bool>> clause = u => u.Name == null;

			var sqlClauseBuilder = new SqlClauseBuilder();

			// Act
			string actualSql = sqlClauseBuilder.Build(clause.Body);

			// Assert
			Assert.AreEqual(expectedSql, actualSql);
		}

		[TestMethod]
		public void SqlClauseBuilderPropertyIsNotNullConst()
		{
			// Arrange
			string expectedSql = string.Format("(Name IS NOT NULL)");

			Expression<Func<User, bool>> clause = u => u.Name != null;

			var sqlClauseBuilder = new SqlClauseBuilder();

			// Act
			string actualSql = sqlClauseBuilder.Build(clause.Body);

			// Assert
			Assert.AreEqual(expectedSql, actualSql);
		}

		[TestMethod]
		public void SqlClauseBuilderAndClause()
		{
			// Arrange
			string name = "some Name";
			string expectedSql = string.Format("((Id = '5') AND (Name = '{0}'))", name);

			Expression<Func<User, bool>> clause = u => u.Id == 5 && u.Name == name;

			var sqlClauseBuilder = new SqlClauseBuilder();

			// Act
			string actualSql = sqlClauseBuilder.Build(clause.Body);

			// Assert
			Assert.AreEqual(expectedSql, actualSql);
		}

		[TestMethod]
		public void SqlClauseBuilderNotClause()
		{
			// Arrange
			int id = 42;
			string expectedSql = string.Format("(Id != '{0}')", id);

			Expression<Func<User, bool>> clause = u => u.Id != id;

			var sqlClauseBuilder = new SqlClauseBuilder();

			// Act
			string actualSql = sqlClauseBuilder.Build(clause.Body);

			// Assert
			Assert.AreEqual(expectedSql, actualSql);
		}

		[TestMethod]
		public void SqlClauseBuilderBooleanClauseWithConst()
		{
			// Arrange
			const string expectedSql = "(IsDisabled != 1)";

			Expression<Func<User, bool>> clause = u => u.IsDisabled != true;

			var sqlClauseBuilder = new SqlClauseBuilder();

			// Act
			string actualSql = sqlClauseBuilder.Build(clause.Body);

			// Assert
			Assert.AreEqual(expectedSql, actualSql);
		}

		[TestMethod]
		public void SqlClauseBuilderBooleanClauseWithValue()
		{
			// Arrange
			bool isDisabled = false;
			string expectedSql = string.Format("(IsDisabled != {0})", isDisabled ? "1" : "0");

			Expression<Func<User, bool>> clause = u => u.IsDisabled != isDisabled;

			var sqlClauseBuilder = new SqlClauseBuilder();

			// Act
			string actualSql = sqlClauseBuilder.Build(clause.Body);

			// Assert
			Assert.AreEqual(expectedSql, actualSql);
		}

		[TestMethod]
		public void SqlClauseBuilderBooleanProperty()
		{
			// Arrange
			const string expectedSql = "(IsDisabled = 1)";

			Expression<Func<User, bool>> clause = u => u.IsDisabled;

			var sqlClauseBuilder = new SqlClauseBuilder();

			// Act
			string actualSql = sqlClauseBuilder.Build(clause.Body);

			// Assert
			Assert.AreEqual(expectedSql, actualSql);
		}

		[TestMethod]
		public void SqlClauseBuilderNotProperty()
		{
			// Arrange
			const string expectedSql = "(IsDisabled = 0)";

			Expression<Func<User, bool>> clause = u => !u.IsDisabled;

			var sqlClauseBuilder = new SqlClauseBuilder();

			// Act
			string actualSql = sqlClauseBuilder.Build(clause.Body);

			// Assert
			Assert.AreEqual(expectedSql, actualSql);
		}

		[TestMethod]
		public void SqlClauseBuilderNotExpression()
		{
			// Arrange
			int id = 42;
			string expectedSql = string.Format("(NOT (Id > '{0}'))", id);

			Expression<Func<User, bool>> clause = u => !(u.Id > id);

			var sqlClauseBuilder = new SqlClauseBuilder();

			// Act
			string actualSql = sqlClauseBuilder.Build(clause.Body);

			// Assert
			Assert.AreEqual(expectedSql, actualSql);
		}

		[TestMethod]
		public void SqlClauseBuilderOrClause()
		{
			// Arrange
			int id = 42;
			string expectedSql = string.Format("((Id = '{0}') OR (Name = 'some Value'))", id);

			Expression<Func<User, bool>> clause = u => u.Id == id || u.Name == "some Value";

			var sqlClauseBuilder = new SqlClauseBuilder();

			// Act
			string actualSql = sqlClauseBuilder.Build(clause.Body);

			// Assert
			Assert.AreEqual(expectedSql, actualSql);
		}

		[TestMethod]
		public void SqlClauseBuilderAndOrClause()
		{
			// Arrange
			int id = 42;
			var now = DateTime.Now;
			string expectedSql = string.Format("((Id = '{0}') OR ((Name = 'some Value') AND (Created < '{1}')))", id, now.ToString("yyyy.MM.dd HH:mm:ss"));

			Expression<Func<User, bool>> clause = u => u.Id == id || (u.Name == "some Value" && u.Created < now);

			var sqlClauseBuilder = new SqlClauseBuilder();

			// Act
			string actualSql = sqlClauseBuilder.Build(clause.Body);

			// Assert
			Assert.AreEqual(expectedSql, actualSql);
		}

		[TestMethod]
		public void SqlClauseBuilderWithBuilderExpression()
		{
			// Arrange
			int id = 42;
			var now = DateTime.Now;
			string expectedSql = string.Format("(((Id = '{0}') AND (Created < '{1}')) AND (Name = 'some Value'))", id, now.ToString("yyyy.MM.dd HH:mm:ss"));

			Expression<Func<User, bool>> clause = u => u.Id == id;
			clause = this.Combine(clause, u => u.Created < now);
			clause = this.Combine(clause, u => u.Name == "some Value");

			var sqlClauseBuilder = new SqlClauseBuilder();

			// Act
			string actualSql = sqlClauseBuilder.Build(clause.Body);

			// Assert
			Assert.AreEqual(expectedSql, actualSql);
		}

		private Expression<Func<TParam, TResult>> Combine<TParam, TResult>(Expression<Func<TParam, TResult>> a, Expression<Func<TParam, TResult>> b)
		{
			if (a == null)
				return b;

			ParameterExpression firstParameter = a.Parameters.First();
			Expression<Func<TParam, TResult>> b1 = Expression.Lambda<Func<TParam, TResult>>(Expression.Invoke(b, firstParameter), firstParameter);
			return Expression.Lambda<Func<TParam, TResult>>(Expression.And(a.Body, b1.Body), firstParameter);
		}

	}
}